﻿using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
namespace SGLib.Net
{
    public class ConcurrentBatchQueue<T>
    {
        private static readonly T m_Null = default(T);

        private object m_Entity;

        private Entity m_BackEntity;

        public bool IsEmpty { get { return Count <= 0; } }

        /// <summary>
        /// Gets the count.
        /// </summary>
        public int Count { get { return ((Entity)m_Entity).Count; } }

        class Entity
        {
            public T[] Array { get; set; }
            public int Count;
        }
        private Func<T, bool> m_NullValidator;

        public ConcurrentBatchQueue() : this(16) { }
        public ConcurrentBatchQueue(int capacity) : this(new T[capacity]) { }
        public ConcurrentBatchQueue(T[] array) : this(array, (t) => t == null) { }
        public ConcurrentBatchQueue(int capacity, Func<T, bool> nullValidator) : this(new T[capacity], nullValidator) { }
        public ConcurrentBatchQueue(T[] array, Func<T, bool> nullValidator)
        {
            var entity = new Entity();
            entity.Array = array;
            m_Entity = entity;
            m_BackEntity = new Entity();
            m_BackEntity.Array = new T[array.Length];
            m_NullValidator = nullValidator;
        }

        public bool Enqueue(T item)
        {
            bool full;
            while (true)
            {
                if (TryEnqueue(item, out full) || full)
                    break;
            }

            return !full;
        }

        public bool TryEnqueue(T item, out bool full)
        {
            full = false;
            Entity entity = this.m_Entity as Entity;
            T[] array = entity.Array;
            int count = entity.Count;


            if (count >= array.Length)
            {
                full = true;
                return false;
            }
            int oldCount = Interlocked.CompareExchange(ref entity.Count, count + 1, count);
            if (oldCount != count)
            {
                return false;
            }
            array[count] = item;
            return true;
        }
        public bool Enqueue(IList<T> items)
        {
            bool full;
            while (true)
            {
                if (TryEnqueue(items, out full) || full)
                    break;
            }
            return !full;
        }

        private bool TryEnqueue(IList<T> items, out bool full)
        {
            full = false;
            var entity = m_Entity as Entity;
            var array = entity.Array;
            var count = entity.Count;

            int newItemCount = items.Count;
            int expectedCount = count + newItemCount;
            if (expectedCount > array.Length)
            {
                full = true;
                return false;
            }
            int oldCount = Interlocked.CompareExchange(ref entity.Count, expectedCount, count);
            if (oldCount != count)
            {
                return false;
            }
            foreach (var item in items)
            {
                array[count++] = item;
            }
            return true;
        }

        public bool TryDequeue(IList<T> outputItems)
        {
            var entity = m_Entity as Entity;
            int count = entity.Count;
            if (count <= 0)
            {
                return false;
            }
            var oldEntity = Interlocked.CompareExchange(ref m_Entity, m_BackEntity, entity);
            if (!ReferenceEquals(oldEntity, entity))
            {
                return false;
            }
            Thread.SpinWait(100);

            count = entity.Count;
            var array = entity.Array;
            int i = 0;
            while (true)
            {
                T item = array[i];
                while (m_NullValidator(item))
                {
                    Thread.SpinWait(100);
                    item = array[i];
                }
                outputItems.Add(item);
                array[i] = m_Null;
                if (entity.Count <= (i + 1))
                {
                    break;
                }
                i++;
            }
            entity.Count = 0;
            m_BackEntity = entity;
            return true;
        }
    }
}